package fr.ul.ade.secunde.sync;

import java.util.List;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import fr.ul.ade.secunde.database.AdeWebContract;
import fr.ul.ade.secunde.util.AdeWebCollections;
import fr.ul.ade.secunde.util.LessonConstants;

/**
 * Classe responsable de la comparaison entre deux versions temporelles d'un
 * même emploi du temps. Génère un rapport regroupant les différences majeures.
 * 
 * @author Michaël Schnell
 * @since Jul 28, 2013
 * 
 * @version 1.3
 */
public abstract class ScheduleComparator {

	private static final String TIME_HOUR_MINUTE_SEPARATOR = ":"; //$NON-NLS-1$

	private static final String MIDNIGHT = "00:00"; //$NON-NLS-1$

	/**
	 * Ancien emploi du temps.
	 */
	protected Cursor oldSchedule;

	/**
	 * Nouvel emploi du temps.
	 */
	protected Cursor newSchedule;

	/**
	 * Rapport de différence entre les deux versions de l'emploi du temps.
	 */
	protected ScheduleDiff diff;

	protected long scheduleId;

	protected Context context;

	protected MatrixCursor displayData;

	private String breakStart;

	/**
	 * Charge les données de l'ancienne version de l'emploi du temps indiqué.
	 * Cette version est récupérée immediatement depuis le
	 * {@link ContentResolver}.
	 * 
	 * @param scheduleId
	 *            identifiant de l'emploi du temps à analyser
	 */
	protected ScheduleComparator(long scheduleId, Context context) {
		this.context = context;
		this.scheduleId = scheduleId;
		this.diff = new ScheduleDiff();
	}

	/**
	 * Initialise le comparateur.
	 */
	public void prepare() {
		this.diff.prepare();

		if (this.oldSchedule != null) {
			this.oldSchedule.close();
		}
		this.oldSchedule = this.context.getContentResolver().query(AdeWebContract.Lesson.buildDisplayUri(this.scheduleId),
				LessonQuery.PROJECTION,
				null,
				null,
				AdeWebContract.Lesson.DEFAULT_SORT);

		if (this.displayData != null) {
			this.displayData.close();
		}
		this.displayData = new MatrixCursor(LessonQuery.DISPLAY_PROJECTION);
	}

	/**
	 * Compare l'emploi du temps fourni avec l'ancienne version et génère le
	 * rapport de différence.
	 * 
	 * @param newSchedule
	 *            version à comparer avec l'ancienne version
	 */
	public final void compare(final Cursor newSchedule) {
		this.newSchedule = newSchedule;
		this.compareS();
	}

	/**
	 * Compare l'emploi du temps fourni avec l'ancienne version et génère le
	 * rapport de différence.
	 */
	protected abstract void compareS();

	/**
	 * Analyse la nouvelle version de l'emploi du temps et génère les
	 * meta-données nécéssaires à l'affichage de la liste des cours du nouvel
	 * emploi du temps.
	 * 
	 * <p>
	 * Ne reprends aucun résultat précédant pour générer les meta-données. Les
	 * sous-classes peuvent surcharger cette méthode pour optimiser ce
	 * comportement.
	 */
	protected void buildDisplay() {
		final Cursor schedule = this.context.getContentResolver().query(AdeWebContract.Lesson.buildLessonUri(this.scheduleId),
				LessonQuery.LESSON_PROJECTION,
				null,
				null,
				AdeWebContract.Lesson.DEFAULT_SORT);
		Log.d("[buildDisplay]", "Found lessons: " + schedule.getCount());
		this.buildDisplay(schedule);
		schedule.close();
	}

	protected void buildDisplay(final Cursor schedule) {
		int baseOrder = 0;
		int currentDayOrder = 0;
		String currentDate = null;

		if (schedule.moveToFirst()) {
			do {
				final String lessonDate = schedule.getString(LessonQuery.LESSON_DATE);

				Log.d("[buildDisplay]", "Lesson: " + schedule.getLong(LessonQuery._ID) + " " + schedule.getString(LessonQuery.LESSON_TITLE) + " " + lessonDate);

				if (!TextUtils.equals(currentDate, lessonDate)) {
					baseOrder += 20;
					currentDayOrder = 0;
					currentDate = lessonDate;

					addDate(currentDate, baseOrder + currentDayOrder);
					currentDayOrder++;
				} else {
					final String start = schedule.getString(LessonQuery.LESSON_START_TIME);
					int breakDuration = getBreakDuration(start);
					if (breakDuration > 5) {
						addBreak(currentDate, baseOrder + currentDayOrder, start, breakDuration);
						currentDayOrder++;
					}
				}
				addLesson(schedule, currentDate, baseOrder + currentDayOrder);
				currentDayOrder++;
			} while (schedule.moveToNext());
		}
	}

	protected void addDate(String date, int order) {
		this.displayData.addRow(new Object[] { null, Long.valueOf(this.scheduleId), null, Integer.valueOf(order), date, MIDNIGHT,
				Integer.valueOf(LessonConstants.LESSON_TYPE_DATE) });
	}

	protected void addBreak(String date, int order, String endTime, int duration) {
		int breakType = LessonConstants.LESSON_TYPE_SMALL_BREAK;
		if (duration > 30) {
			breakType = LessonConstants.LESSON_TYPE_BREAK;
		}
		this.displayData.addRow(new Object[] { null, Long.valueOf(this.scheduleId), null, Integer.valueOf(order), date, endTime, Integer.valueOf(breakType) });
	}

	protected void addLesson(final Cursor lesson, String date, int order) {
		long lessonId = lesson.getLong(LessonQuery._ID);
		this.breakStart = lesson.getString(LessonQuery.LESSON_END_TIME);
		this.displayData.addRow(new Object[] { null, Long.valueOf(this.scheduleId), Long.valueOf(lessonId), Integer.valueOf(order), null, null,
				Integer.valueOf(LessonConstants.LESSON_TYPE_LESSON) });
	}

	/**
	 * Calcule la durée (en minutes) entre le temps fourni et le dernier cours
	 * ajouté dans {@link #displayData}.
	 * 
	 * @param end
	 * @return
	 */
	protected int getBreakDuration(String end) {
		if (this.displayData.moveToLast()) {
			return timeToMinutes(end) - timeToMinutes(this.breakStart);
		}
		return -1;
	}

	/**
	 * @param time
	 * @return
	 */
	private static int timeToMinutes(final String time) {
		final String[] timeParts = time.split(TIME_HOUR_MINUTE_SEPARATOR);
		return Integer.parseInt(timeParts[0]) * 60 + Integer.parseInt(timeParts[1]);
	}

	// /**
	// * @param minutes
	// * @return
	// */
	// private static String minutesToTime(int minutes) {
	// return (minutes / 60) + TIME_HOUR_MINUTE_SEPARATOR + (minutes % 60);
	// }

	public void commitChanges() {
		// FIXME Add commit code
		final ContentResolver contentResolver = this.context.getContentResolver();

		final Cursor inserted = this.diff.getInsertedLessons();
		if (inserted.getCount() > 0) {
			ContentValues[] values = new ContentValues[inserted.getCount()];
			inserted.moveToFirst();
			do {
				values[inserted.getPosition()] = lessonCursorToContentValues(inserted, this.scheduleId);
			} while (inserted.moveToNext());
			int added = contentResolver.bulkInsert(AdeWebContract.Lesson.buildLessonUri(this.scheduleId), values);
			Log.d("[commitDisplay]", "Added lessons: " + added + "(" + this.newSchedule.getCount() + ")");
		}

		final Cursor modified = this.diff.getModifiedLessons();
		if (modified.getCount() > 0) {
			while (modified.moveToNext()) {
				contentResolver.update(AdeWebContract.Lesson.buildLessonUri(modified.getLong(LessonQuery.LESSON_SCHEDULE)),
						lessonCursorToContentValues(modified),
						null,
						null);
			}
		}

		final Cursor deleted = this.diff.getDeletedLessons();
		if (deleted.getCount() > 0) {
			List<Long> deletedIds = AdeWebCollections.newArrayList();
			while (deleted.moveToNext()) {
				deletedIds.add(Long.valueOf(deleted.getLong(LessonQuery._ID)));
			}
			final String selection = String.format(AdeWebContract.Lesson.QUERY_ID_IN, TextUtils.join(AdeWebContract.Lesson.ID_SEPARATOR, deletedIds));

			ContentValues values = new ContentValues();
			values.put(AdeWebContract.Lesson.LESSON_DELETED, LessonConstants.DELETED_TRUE);

			contentResolver.update(AdeWebContract.Lesson.buildLessonUri(this.scheduleId), values, selection, null);
		}
	}

	/**
	 * Insère les meta-données pour l'affichage de la liste des cours dans la
	 * base de données.
	 */
	public void commitDisplay() {
		this.buildDisplay();

		final ContentResolver contentResolver = this.context.getContentResolver();
		final Uri lessonDisplayUri = AdeWebContract.Lesson.buildDisplayUri(this.scheduleId);

		contentResolver.delete(lessonDisplayUri, null, null);

		if (this.displayData.getCount() > 0) {
			ContentValues[] values = new ContentValues[this.displayData.getCount()];
			this.displayData.moveToFirst();
			do {
				values[this.displayData.getPosition()] = lessonDisplayCursorToContentValues(this.displayData);
			} while (this.displayData.moveToNext());
			int added = contentResolver.bulkInsert(lessonDisplayUri, values);

			Log.d("[commitDisplay]", "Added display elements: " + added + "(" + this.displayData.getCount() + ")");
		}
	}

	private static ContentValues lessonCursorToContentValues(final Cursor lesson) {
		return lessonCursorToContentValues(lesson, lesson.getLong(LessonQuery.LESSON_SCHEDULE));
	}

	private static ContentValues lessonCursorToContentValues(final Cursor lesson, long scheduleId) {
		final ContentValues values = new ContentValues();
		values.put(AdeWebContract.Lesson.LESSON_TITLE, lesson.getString(LessonQuery.LESSON_TITLE));
		values.put(AdeWebContract.Lesson.LESSON_DATE, lesson.getString(LessonQuery.LESSON_DATE));
		values.put(AdeWebContract.Lesson.LESSON_START_TIME, lesson.getString(LessonQuery.LESSON_START_TIME));
		values.put(AdeWebContract.Lesson.LESSON_END_TIME, lesson.getString(LessonQuery.LESSON_END_TIME));
		values.put(AdeWebContract.Lesson.LESSON_SCHEDULE, Long.valueOf(scheduleId));
		values.put(AdeWebContract.Lesson.LESSON_CHANGE, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_CHANGE)));
		values.put(AdeWebContract.Lesson.LESSON_ROOMS, lesson.getString(LessonQuery.LESSON_ROOMS));
		values.put(AdeWebContract.Lesson.LESSON_GROUPS, lesson.getString(LessonQuery.LESSON_GROUPS));
		values.put(AdeWebContract.Lesson.LESSON_TEACHERS, lesson.getString(LessonQuery.LESSON_TEACHERS));
		values.put(AdeWebContract.Lesson.LESSON_COLOR, lesson.getString(LessonQuery.LESSON_COLOR));
		values.put(AdeWebContract.Lesson.LESSON_NOTE, lesson.getString(LessonQuery.LESSON_NOTE));
		values.put(AdeWebContract.Lesson.LESSON_GUID, lesson.getString(LessonQuery.LESSON_GUID));
		values.put(AdeWebContract.Lesson.LESSON_DELETED, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_DELETED)));
		values.put(AdeWebContract.Lesson.LESSON_LAST_UPDATE, lesson.getString(LessonQuery.LESSON_LAST_UPDATE));
		values.put(AdeWebContract.Lesson.LESSON_OLD_DATE, lesson.getString(LessonQuery.LESSON_OLD_DATE));
		values.put(AdeWebContract.Lesson.LESSON_OLD_START_TIME, lesson.getString(LessonQuery.LESSON_OLD_START_TIME));
		values.put(AdeWebContract.Lesson.LESSON_OLD_END_TIME, lesson.getString(LessonQuery.LESSON_OLD_END_TIME));
		values.put(AdeWebContract.Lesson.LESSON_OLD_ROOMS, lesson.getString(LessonQuery.LESSON_OLD_ROOMS));
		values.put(AdeWebContract.Lesson.LESSON_CREATED, lesson.getString(LessonQuery.LESSON_CREATED));
		values.put(AdeWebContract.Lesson.LESSON_ORDER, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_ORDER)));
		values.put(AdeWebContract.Lesson.LESSON_TYPE, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_TYPE)));
		return values;
	}

	private static ContentValues lessonDisplayCursorToContentValues(final Cursor lesson) {
		final ContentValues values = new ContentValues();
		values.put(AdeWebContract.Lesson.LESSON_SCHEDULE, Long.valueOf(lesson.getLong(LessonQuery.LESSON_DISPLAY_SCHEDULE)));
		values.put(AdeWebContract.Lesson.LESSON_ID, Long.valueOf(lesson.getLong(LessonQuery.LESSON_DISPLAY_ID)));
		values.put(AdeWebContract.Lesson.LESSON_DATE, lesson.getString(LessonQuery.LESSON_DISPLAY_DATE));
		values.put(AdeWebContract.Lesson.LESSON_END_TIME, lesson.getString(LessonQuery.LESSON_DISPLAY_END_TIME));
		values.put(AdeWebContract.Lesson.LESSON_ORDER, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_DISPLAY_ORDER)));
		values.put(AdeWebContract.Lesson.LESSON_TYPE, Integer.valueOf(lesson.getInt(LessonQuery.LESSON_DISPLAY_TYPE)));
		return values;
	}

}// class ScheduleComparator